#import "DetailViewController.h"
#import "DetailViewController+Print.h"
#import "MainWindowController.h"
#import "RNDetailView.h"
#import "RNItemCell.h"
#import "RNListItemCell.h"
#import "RNListContainerCell.h"
#import "RNEmptyItemCell.h"
#import "RNDetailHeaderCell.h"
#import "RNContainerCell.h"
#import "RNCategory.h"
#import "RNStore.h"
#import "RNAisle.h"
#import "RNItem.h"
#import "RNList.h"
#import "RNListItem.h"
#import "RNConstants.h"
#import "RNEditListItemViewController.h"
#import "RNEditItemViewController.h"
#import "RNEditViewController.h"
#import "RNStepper.h"
#import "NSManagedObjectContext+SRAdditions.h"
#import "RNDetailHeader.h"
#import "NSTreeController+SRAdditions.h"
#import "NSBrowser+SRAdditions.h"
#import "RNCatalog.h"
#import "RNFavorites.h"
#import "RNHistory.h"
#import "SearchSubHeader.h"
#import "RNHistoryItem.h"
#import "RNPrintListContainerCell.h"
#import "RNPrintContainerCell.h"
#import "RNPrintListItemCell.h"
#import "RNSearchSubheaderCell.h"
#import "RNHistoryItemCell.h"

#pragma mark -
@interface DetailViewController (DetailViewControllerPrivateMethods)
-(void)initializeState_;
-(void)removeObject:(NSManagedObject *)object displayWarning:(BOOL)displayWarning;
-(void)initiateSearchWithString_:(NSString *)searchString;
-(id)rootObject;
-(void)setRootObject:(id)rootObject;
-(void)updateStoreFilterSelectionWithStore:(RNStore *)store;
-(BOOL)allSelectedItemsAreListItems_;
@end

#pragma mark -
@implementation DetailViewController

@synthesize detailScrollView = detailScrollView_;
@synthesize expandGroups = expandGroups_;
@synthesize currentDetailHeaders = currentDetailHeaders_;
@synthesize detailCells = detailCells_;
@synthesize detailView = detailView_;
@synthesize editListItemViewController = editListItemViewController_;
@synthesize editItemViewController = editItemViewController_;
@synthesize editViewController = editViewController_;
@synthesize storeFilter = storeFilter_;
@synthesize detailTreeController = detailViewController_;
@synthesize titleAndIndexSortDescriptors = titleAndIndexSortDescriptors_;
@synthesize loggedDateSortDescriptors = loggedDateSortDescriptors_;
@synthesize currentSortDescriptors = currentSortDescriptors_;

// Change category sheet
@synthesize changeCategorySheet = changeCategorySheet_;
@synthesize changeCategoryTitleField = changeCategoryTitleField_;
@synthesize changeCategoryBrowser = changeCategoryBrowser_;
@synthesize categoriesControllerSortDescriptors = categoriesControllerSortDescriptors_;
@synthesize itemsToChange = itemsToChange_;
@synthesize sheetTitle = sheetTitle_;

// Change aisle sheet
@synthesize changeAisleSheet = changeAisleSheet_;
@synthesize changeAisleTitleField = changeAisleTitleField_;
@synthesize aislesController = aislesController_;
@synthesize storesController = storesController_;
@synthesize titleDescriptor = titleDescriptor_;
@synthesize aisleNumberDescriptor = aisleNumberDescriptor_;

// Add list item sheet
@synthesize addListItemSearchField = addListItemSearchField_;
@synthesize filteredItemsArrayController = filteredItemsController_;
@synthesize addListItemControllerDescriptors = addListItemControllerDescriptors_;
@synthesize addListItemTitleField = addListItemTitleField_;
@synthesize searchResultsField = searchResultsField_;
@synthesize searchResultsTable = searchResultsTable_;
@synthesize addListItemSheet = addListItemSheet_;
@synthesize addAndRepeatButton = addAndRepeatButton_;
@synthesize cancelAddListItemButton = cancelAddListItemButton_;

// Print view
@synthesize printView = printView_;
@synthesize printDetailView = printDetailView_;
@synthesize titleField = titleField_;
@synthesize printDetailScrollView = printDetailScrollView_;

// Application search
@synthesize applicationSearchField = applicationSearchField_;
@synthesize cachedSearchType = cachedSearchType_;

// Detail Guide Views
@synthesize currentDetailGuideView = currentDetailGuideView_;
@synthesize emptyListGuide = emptyListGuide_;
@synthesize favoritesGuide = favoritesGuide_;
@synthesize historyGuide = historyGuide_;
@synthesize storeGuide = storeGuide_;

#pragma mark -
#pragma mark Initialization

- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle windowController:(XSWindowController *)windowController managedObjectContext:(NSManagedObjectContext *)managedObjectContext {
    if (!(self = [super initWithNibName:name bundle:bundle windowController:windowController managedObjectContext:managedObjectContext]))
        return nil;
    
    [self initializeState_];
    return self;
}

-(id)initWithCoder:(NSCoder *)coder {
    if (!(self = [super initWithCoder:coder]))
        return nil;
    
    [self initializeState_];
    return self;
}

-(void)initializeState_ {
	NSComparator titleSortBlock = ^(id string1, id string2) {
		return [string1 compare:string2 options:NSCaseInsensitiveSearch | NSNumericSearch];
	};
	NSSortDescriptor *titleSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:RNTitleKey ascending:YES comparator:titleSortBlock];
    [self setTitleDescriptor:[NSArray arrayWithObject:titleSortDescriptor]];
	[self setAisleNumberDescriptor:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"aisleNumber" ascending:YES]]];
//    NSSortDescriptor *titleDescriptor = [NSSortDescriptor sortDescriptorWithKey:RNTitleKey ascending:YES comparator:titleSortBlock];
    NSSortDescriptor *indexDescriptor = [[NSSortDescriptor alloc] initWithKey:RNDisplayIndexKey ascending:YES];
    [self setTitleAndIndexSortDescriptors:[NSArray arrayWithObjects:indexDescriptor, titleSortDescriptor, nil]];
    [self setLoggedDateSortDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"loggedDate" ascending:NO]]];
    
    [self setCurrentDetailHeaders:[NSMutableArray array]];
    NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
    [noteCenter addObserver:self selector:@selector(editItemViewDidHide:) name:RNEditItemViewDidHideNotification object:nil];
    [noteCenter addObserver:self selector:@selector(managedObjectsDidChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]];
    [noteCenter addObserver:self selector:@selector(listItemChangedCompletionState:) name:RNListItemCompletionChangedNotification object:nil];
    [[self windowController] addObserver:self forKeyPath:@"rootObject" options:NSKeyValueObservingOptionNew context:nil];
}

-(void)awakeFromNib {
    [[self detailView] setTarget:self];
    NSSortDescriptor *leafDescriptor = [[NSSortDescriptor alloc] initWithKey:@"isLeaf" ascending:YES];
//    NSSortDescriptor *titleDescriptor = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES];
    [self setCategoriesControllerSortDescriptors:[NSArray arrayWithObjects:leafDescriptor, [[self titleDescriptor] objectAtIndex:0], nil]];
//    [self setTitleDescriptor:[NSArray arrayWithObject:titleDescriptor]];
    [[self detailView] registerForDraggedTypes:[NSArray arrayWithObject:RNDragDropUTI]];
    [[self detailView] setDraggingSourceOperationMask:NSDragOperationMove | NSDragOperationCopy forLocal:YES];
    [[self detailView] setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleNone];
    NSImage *detailBackgroundImage = [NSImage imageNamed:@"DetailTexture"];
    NSColor *detailBackgroundColor = [NSColor colorWithPatternImage:detailBackgroundImage];
    [[self detailView] setBackgroundColor:detailBackgroundColor];
}

 
#pragma mark -
#pragma mark Accessors

-(RNEditListItemViewController *)editListItemViewController {
    if (!editListItemViewController_) {
        editListItemViewController_ = [[RNEditListItemViewController alloc] initWithNibName:@"EditListItemView" bundle:[NSBundle mainBundle] windowController:[self windowController] managedObjectContext:[self managedObjectContext]];  
        [self addChild:editListItemViewController_];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editItemViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:[editListItemViewController_ view]]; 
    }
    return editListItemViewController_;
}

-(RNEditItemViewController *)editItemViewController {
    if (!editItemViewController_) {
        editItemViewController_ = [[RNEditItemViewController alloc] initWithNibName:@"EditItemView" bundle:[NSBundle mainBundle] windowController:[self windowController] managedObjectContext:[self managedObjectContext]];
        [self addChild:editItemViewController_];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editItemViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:[[self editItemViewController] view]];
    }
    return editItemViewController_;
}

-(NSMutableDictionary *)detailCells {
    if (!detailCells_) {
        detailCells_ = [NSMutableDictionary dictionary];
        [detailCells_ setValue:[[RNItemCell alloc] initTextCell:@""] forKey:RNEntityNameItem];
        [detailCells_ setValue:[[RNListItemCell alloc] initTextCell:@""] forKey:RNEntityNameListItem];
        RNListContainerCell *listContainerCell = [[RNListContainerCell alloc] initTextCell:@""];
        [listContainerCell setAction:@selector(loadRootObjectFromCell:)];
        [listContainerCell setTarget:self];
        [detailCells_ setValue:listContainerCell forKey:RNListContainerCellKey];
        [detailCells_ setValue:[[RNEmptyItemCell alloc] initTextCell:@""] forKey:RNEmptyCellKey];
        RNDetailHeaderCell *detailheaderCell = [[RNDetailHeaderCell alloc] initTextCell:@""];
        [detailheaderCell setAction:@selector(swapShowsChildrenAndReload:)];
        [detailheaderCell setTarget:self];
        [detailCells_ setValue:detailheaderCell forKey:RNDetailHeaderCellKey];
        [detailCells_ setValue:[[RNSearchSubheaderCell alloc] initTextCell:@""] forKey:RNSearchSubheaderCellKey];
		RNContainerCell *containerCell = [[RNContainerCell alloc] initTextCell:@""];
		[containerCell setAction:@selector(loadRootObjectFromCell:)];
        [detailCells_ setValue:containerCell forKey:RNContainerCellKey];
        [detailCells_ setValue:[[RNPrintListContainerCell alloc] initTextCell:@""] forKey:RNPrintListContainerCellKey];
        [detailCells_ setValue:[[RNPrintListItemCell alloc] initTextCell:@""] forKey:RNPrintListItemCellKey];
        [detailCells_ setValue:[[RNPrintContainerCell alloc] initTextCell:@""] forKey:RNPrintContainerCellKey];
        [detailCells_ setValue:[[RNHistoryItemCell alloc] initTextCell:@""] forKey:RNHistoryItemCellKey];
    }
    return detailCells_;
}

+(NSSet *)keyPathsForValuesAffectingRootObject {
    return [NSSet setWithObject:@"windowController.rootObject"];
}

-(id)rootObject {
    return [(MainWindowController *)[self windowController] rootObject];
}

-(void)setRootObject:(id)rootObject {
    [(MainWindowController *)[self windowController] selectRootObject:rootObject];
}

-(void)updateDetailGuide {
    NSString *guideViewKey = nil;
    id root = [self rootObject];
    if ([root isKindOfClass:[RNList class]] && [[root items] count] == 0) 
        guideViewKey = @"emptyListGuide";
    else if ([root isKindOfClass:[RNFavorites class]]) {
        NSArray *favorites = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameItem withPredicate:@"favorite == YES"];
        if ([favorites count] == 0)
            guideViewKey = @"favoritesGuide";
    }
    else if ([root isKindOfClass:[RNHistory class]]) {
        NSArray *history = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameHistoryItem withPredicate:nil];
        if ([history count] == 0)
            guideViewKey = @"historyGuide";
    }
    else if ([root isKindOfClass:[RNStore class]] && [[root aisles] count] == 0)
        guideViewKey = @"storeGuide";
    
    if (!guideViewKey) {
        [[self currentDetailGuideView] removeFromSuperview];
        return;
    }
    NSView *guideView = [self valueForKey:guideViewKey];
    if (!guideView) {
        if (![NSBundle loadNibNamed:@"DetailViewGuides" owner:self])
            return;
        guideView = [self valueForKey:guideViewKey];
    }
    if ([guideView superview] == [self detailView])
        return;
    [[self currentDetailGuideView] removeFromSuperview];
    [self setCurrentDetailGuideView:guideView];
    NSRect detailRect = [[self detailView] frame];
    [[self currentDetailGuideView] setFrame:detailRect];
    [[self detailView] addSubview:[self currentDetailGuideView]];
}

#pragma mark -
#pragma mark Notifications

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"arrangedObjects"]) {
        [self updateDetailGuide];
        [[self detailView] expandItem:nil expandChildren:YES];
        [[self detailView] noteNumberOfRowsChanged]; // Without this method call, I was getting a nil item sent to heightOfRowByItem
        return;
    }
    
    if (![keyPath isEqualToString:@"rootObject"])
        return;
    
    [self updateDetailGuide];
    
    if (![self rootObject]) {
        [self setCurrentDetailHeaders:nil];
        return;
    }
    
    BOOL rootObjectIsList = [[self rootObject] isKindOfClass:[RNList class]];
    if (rootObjectIsList) {
        NSURL *uri = [[NSUserDefaults standardUserDefaults] URLForKey:[NSString stringWithFormat:@"%@%@", [[self rootObject] valueForKeyPath:RNTitleKey], RNStoreFilterKey]];
        RNStore *storeFilter = nil;
        if (uri)
            storeFilter = (RNStore *)[[self managedObjectContext] objectWithURI:uri];
        [self updateStoreFilterSelectionWithStore:storeFilter];
        [self setStoreFilter:storeFilter];
    }
    if (![[self rootObject] isKindOfClass:[RNSearch class]]) {
        if ([self cachedSearchType] > 0) {
            [[RNSearch sharedSearch] setApplicationSearchType:[self cachedSearchType]];
            [self setCachedSearchType:0];
        }
    }
    [self reloadDetailView];
}

-(void)managedObjectsDidChange:(NSNotification *)note {
    NSDictionary *userInfo = [note userInfo];
    NSSet *inserted = [[note userInfo] valueForKey:NSInsertedObjectsKey];
	if ([inserted count] > 0) {
		id insertedObject = [inserted anyObject];
		id parentObject = nil;
		if ([insertedObject isKindOfClass:[RNCategory class]])
			parentObject = [insertedObject parentCategory];
		else if ([insertedObject isKindOfClass:[RNItem class]]) {
			if ([[self rootObject] isKindOfClass:[RNAisle class]] || [[self rootObject] isKindOfClass:[RNStore class]]) {
				RNStore *currentStore = [[self rootObject] isKindOfClass:[RNStore class]] ? [self rootObject] : [[self rootObject] store];
				NSSet *aisles = [insertedObject aisles];
				NSSet *storeAisles = [aisles filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"store == %@", currentStore]];
				parentObject = [storeAisles count] > 0 ? [storeAisles anyObject] : [insertedObject category];
			}
			else if ([[self rootObject] isKindOfClass:[RNList class]]) {
				RNListItem *newListItem = [NSEntityDescription insertNewObjectForEntityForName:RNEntityNameListItem inManagedObjectContext:[self managedObjectContext]];
				[newListItem setItem:insertedObject];
				[newListItem setCategory:[insertedObject category]];
				[newListItem addAisles:[insertedObject aisles]];
				[newListItem setList:[self rootObject]];
				parentObject = [self rootObject];
				insertedObject = newListItem;
			}
			else if ([[self rootObject] isKindOfClass:[RNCategory class]])
				parentObject = [self rootObject];				
		}
		else if ([insertedObject isKindOfClass:[RNAisle class]])
			parentObject = [insertedObject store];
		if (parentObject)
			[self setRootObject:parentObject];
		NSTreeNode *insertedNode = [[self detailTreeController] treeNodeForObject:insertedObject];
		if (insertedNode)
			[[self detailTreeController] setSelectionIndexPath:[insertedNode indexPath]];
//		NSLog(@"inserted object %@ with node %@", insertedObject, insertedNode);
	}
	
    NSSet *deletedObjects = [userInfo valueForKey:NSDeletedObjectsKey];
    if ([deletedObjects count] == 0)
        return;
    NSSet *deletedStores = [deletedObjects filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", RNEntityNameStore]];
    if ([deletedStores count] == 0)
        return;
    
    NSArray *lists = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameList withPredicate:nil];
    for (RNList *list in lists) {
        NSString *listKey = [NSString stringWithFormat:@"%@%@", [list title], RNStoreFilterKey];
        NSURL *uri = [[NSUserDefaults standardUserDefaults] URLForKey:listKey];
        RNStore *cachedStoreFilter = nil;
        if (uri)
            cachedStoreFilter = (RNStore *)[[self managedObjectContext] objectWithURI:uri];

        if ([deletedStores containsObject:cachedStoreFilter]) 
            [[NSUserDefaults standardUserDefaults] removeObjectForKey:listKey];
    }
}

-(void)removeAndLogListItems:(NSArray *)listItems {
    for (RNListItem *listItem in listItems) {
        NSManagedObject *historyItem = [NSEntityDescription insertNewObjectForEntityForName:RNEntityNameHistoryItem inManagedObjectContext:[self managedObjectContext]];
        [historyItem setValue:[listItem count] forKey:@"count"];
        [historyItem setValue:[listItem notes] forKey:@"notes"];
        [historyItem setValue:[listItem tags] forKey:@"tags"];
        [historyItem setValue:[NSDate date] forKey:@"loggedDate"];
        [historyItem setValue:[listItem item] forKey:@"item"];
        [[self managedObjectContext] deleteObject:listItem];
    }
}

-(void)updatePostCompletionDisplayOfListItems:(NSArray *)listItems {
    NSNumber *logCompletedItemsInterval = [[NSUserDefaults standardUserDefaults] objectForKey:RNLogCompletedItemsIntervalKey];
    if (logCompletedItemsInterval != nil && [logCompletedItemsInterval integerValue] == RNLogCompletedItemsImmediately) {
        [self removeAndLogListItems:listItems];
    }
    [[self detailView] setNeedsDisplay:YES];
}

-(void)listItemChangedCompletionState:(NSNotification *)note {
    [self updatePostCompletionDisplayOfListItems:[NSArray arrayWithObject:[note object]]];
}

-(void)editItemViewFrameDidChange:(NSNotification *)note {
    NSTreeNode *editNode = [[self detailTreeController] treeNodeForObject:[[self editViewController] editingItem]];
    NSInteger rowIndex = [[self detailView] rowForItem:editNode];
    [[self detailView] noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndex:rowIndex]];
}

-(void)editItemViewDidHide:(NSNotification *)notification {
    NSTreeNode *editNode = [[self detailTreeController] treeNodeForObject:[notification object]];
    NSInteger rowIndex = [[self detailView] rowForItem:editNode];
    [[self detailView] noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndex:rowIndex]];
}


#pragma mark -
#pragma mark Actions

// This is sent by RNDetailHeaderCell when the reveal text is clicked
-(IBAction)swapShowsChildrenAndReload:(id)sender {
    RNDetailHeader *detailHeader = [sender representedObject];
    [detailHeader setShowsChildren:![detailHeader showsChildren]];
    // TODO: SEE IF YOU CAN REPLACE noteNumberOfRowsChanged WITH noteHeightOfRowsWithIndexes...
    NSTreeNode *itemNode = [[self detailTreeController] treeNodeForObject:detailHeader];
    [[self detailView] expandItem:itemNode];
    [[self detailView] noteNumberOfRowsChanged]; // Without this method call, I was getting a nil item sent to heightOfRowByItem
}

// This is sent by RNListContainerCell when the text is clicked
-(IBAction)loadRootObjectFromCell:(id)sender {
    [self setRootObject:[sender representedObject]];
    [[self detailView] setTrackingRow:-1];
    [[self detailView] setTrackingCell:nil];
}

-(BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem {
    SEL action = [anItem action];
    if (action == @selector(changeCategoryForSelectedItems:) || action == @selector(changeAisleForSelectedItems:)) {
        NSIndexSet *selectedIndexes = [[self detailView] selectedRowIndexes];
        if ([selectedIndexes count] == 0)
            return NO;
        __block BOOL returnValue = YES;
        [selectedIndexes enumerateIndexesUsingBlock:^(NSUInteger selectedIndex, BOOL *stop) { 
            id object = [[[self detailView] itemAtRow:selectedIndex] representedObject];
            if (![object isKindOfClass:[RNItem class]] && ![object isKindOfClass:[RNListItem class]]) {
                returnValue = NO;
                *stop = YES;
            }
        }];
        return returnValue;
    }
    if (action == @selector(toggleComplete:)) {
        NSIndexSet *selectedIndexes = [[self detailView] selectedRowIndexes];
        __block BOOL returnValue = YES, markAsComplete = NO;
        if ([selectedIndexes count] == 0) {
            markAsComplete = YES;
            returnValue = NO;
        }
        [selectedIndexes enumerateIndexesUsingBlock:^(NSUInteger selectedIndex, BOOL *stop) { 
            id object = [[[self detailView] itemAtRow:selectedIndex] representedObject];
            if (![object isKindOfClass:[RNListItem class]]) {
                returnValue = NO;
                markAsComplete = YES;
            }
            else if ([[object complete] boolValue] == NO) {
                markAsComplete = YES;
            }

        }];
        [(NSMenuItem *)anItem setTitle:markAsComplete ? @"Mark As Complete" : @"Mark As Not Complete"];
        return returnValue;
    }
    if (action == @selector(clearCompletedItems:)) {
        id root = [self rootObject];
        if (![root isKindOfClass:[RNList class]])
            return NO;
        NSSet *items = [(RNList *)root items];
        NSSet *completeItems = [items filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"complete == %@", [NSNumber numberWithBool:YES]]];
        return [completeItems count] > 0;
    }
    if (action == @selector(displayAddListItemSheet:)) {
        return [[self rootObject] isKindOfClass:[RNList class]];
    }
    if (action == @selector(incrementCount:) || action == @selector(decrementCount:)) {
        return [self allSelectedItemsAreListItems_];
    }
    if (action == @selector(editSelectedItem:) || action == @selector(deleteSelectedItems:) || action == @selector(cut:) || action == @selector(copy:)) {
        NSIndexSet *selectedIndexes = [[self detailView] selectedRowIndexes];
        return [selectedIndexes count] > 0;
    }
	if (action == @selector(openContainer:)) {
		NSArray *selectedItems = [[self detailTreeController] selectedObjects];
		if ([selectedItems count] != 1) 
			return NO;
		id selectedItem = [selectedItems objectAtIndex:0];
		if ([selectedItem isKindOfClass:[RNCategory class]] || [selectedItem isKindOfClass:[RNAisle class]])
			return YES;
		return NO;
	}

    return YES;
}


-(IBAction)resetStoreFilter:(id)sender {
    RNStore *selectedStore = [[sender selectedItem] representedObject];
    [self setStoreFilter:selectedStore];
    NSString *listKey = [NSString stringWithFormat:@"%@%@", [[self rootObject] valueForKey:RNTitleKey], RNStoreFilterKey];
    // Save the selected store as the default for the selected list
    if (selectedStore) {
        NSURL *uri = [[selectedStore objectID] URIRepresentation];
        [[NSUserDefaults standardUserDefaults] setURL:uri forKey:listKey];
    }
    else // All Stores
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:listKey];
    
    [self reloadDetailView];
}

-(IBAction)setSearchType:(id)sender {
    NSMenu *applicationSearchTypeMenu = [sender menu];
    NSMenuItem *currentSelectedItem = [applicationSearchTypeMenu itemWithTag:[[RNSearch sharedSearch] applicationSearchType]];
    [currentSelectedItem setState:NSOffState];
    [sender setState:NSOnState];
    [[RNSearch sharedSearch] setApplicationSearchType:[sender tag]];
    [[NSUserDefaults standardUserDefaults] setInteger:[sender tag] forKey:RNApplicationSearchTypeKey];
}

-(IBAction)applicationSearchStringChanged:(id)sender {
    NSString *searchFilterString = [sender stringValue];
    NSAssert(searchFilterString != nil, @"DetailViewController: search string is nil");
    [self initiateSearchWithString_:searchFilterString];
}

-(void)initiateSearchWithString_:(NSString *)searchString {
    [[RNSearch sharedSearch] setSearchString:searchString];
    if ([searchString length] == 0 && [self rootObject] == [RNSearch sharedSearch]) 
        [(MainWindowController *)[self windowController] selectPreviousRootObject:self];    
    else if ([self rootObject] != [RNSearch sharedSearch]) 
        [self setRootObject:[RNSearch sharedSearch]];
}

-(void)clearCompletedItemsInList:(RNList *)list {
    NSSet *completedItems = [[list items] filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"complete == TRUE"]];
    [self removeAndLogListItems:[completedItems allObjects]];
}

-(void)clearCompletedItemsInAllLists {
    NSArray *allLists = [[self managedObjectContext] fetchObjectsForEntityName:RNEntityNameList withPredicate:nil];
    for (RNList *list in allLists)
        [self clearCompletedItemsInList:list];
    NSCalendar *currentCalendar = [NSCalendar currentCalendar];
    NSDateComponents *normalizedComponents = [currentCalendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:[NSDate date]];
    NSDate *clearCompletedItemsDate = [currentCalendar dateFromComponents:normalizedComponents];
    [[NSUserDefaults standardUserDefaults] setObject:clearCompletedItemsDate forKey:RNLogCompletedItemsDateKey];
}

-(IBAction)clearCompletedItems:(id)sender {
    if (![[self rootObject] isKindOfClass:[RNList class]])
        return;
    [self clearCompletedItemsInList:[self rootObject]];
}

-(IBAction)toggleComplete:(id)sender {
    BOOL markAsComplete = NO;
    NSArray *selectedItems = [[self detailTreeController] selectedObjects];
    NSArray *selectedNonListItems = [selectedItems filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name != %@", RNEntityNameListItem]];
    if ([selectedNonListItems count] > 0) {
        NSBeep();
        return;
    }
    NSArray *incompletes = [selectedItems filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"complete == %@", [NSNumber numberWithBool:NO]]];
    if ([incompletes count] > 0)
        markAsComplete = YES;
    [selectedItems makeObjectsPerformSelector:@selector(setComplete:) withObject:[NSNumber numberWithBool:markAsComplete]];
    [self updatePostCompletionDisplayOfListItems:selectedItems];
}

-(IBAction)deleteSelectedItems:(id)sender {
    NSIndexSet *selectedRows = [[self detailView] selectedRowIndexes];
    if ([selectedRows count] == 0) {
        NSBeep();
        return;
    }
    NSMutableArray *selectedObjects = [NSMutableArray arrayWithCapacity:[selectedRows count]];
    for (NSUInteger rowIndex = [selectedRows firstIndex]; rowIndex != NSNotFound; rowIndex = [selectedRows indexGreaterThanIndex:rowIndex]) {
        NSManagedObject *object = [[[self detailView] itemAtRow:rowIndex] representedObject];
        if (!object) {
            NSLog(@"deleting nil item at row %i", rowIndex);
            continue;
        }
        [selectedObjects addObject:object];
    }
    for (NSManagedObject *object in selectedObjects) {
        if ([[self rootObject] isKindOfClass:[RNFavorites class]])
            [(RNItem *)object setFavorite:[NSNumber numberWithBool:NO]];
        else if ([[self rootObject] isKindOfClass:[RNAisle class]]) {
            [[self rootObject] removeItemsObject:(RNItem *)object];
            [[self rootObject] removeListItems:[(RNItem *)object listItems]];
        }
        else {
            [self removeObject:object displayWarning:NO];
        }
    }
}

-(IBAction)incrementCount:(id)sender {
    if (![self allSelectedItemsAreListItems_])
        return;
    NSArray *selectedItems = [[self detailTreeController] selectedObjects];
    for (RNListItem *listItem in selectedItems) {
        NSInteger count = [[listItem count] integerValue];
        NSNumber *newCount = [NSNumber numberWithInteger:++count];
        [listItem setCount:newCount];
    }
}

-(IBAction)decrementCount:(id)sender {
    if (![self allSelectedItemsAreListItems_])
        return;
    NSArray *selectedItems = [[self detailTreeController] selectedObjects];
    for (RNListItem *listItem in selectedItems) {
        NSInteger count = [[listItem count] integerValue] - 1;
        if (count < 1)
            count = 1;
        NSNumber *newCount = [NSNumber numberWithInteger:count];
        [listItem setCount:newCount];
    }
}

-(IBAction)openContainer:(id)sender {
	NSArray *selectedItems = [[self detailTreeController] selectedObjects];
	if ([selectedItems count] != 1)
		return;
	id selectedItem = [selectedItems objectAtIndex:0];
	[self setRootObject:selectedItem];
}

#pragma mark -
#pragma mark Layout methods

-(void)showEditItemViewAtRow:(NSInteger)rowIndex {
    if (![[self detailView] isRowSelected:rowIndex])
        [[self detailView] selectRowIndexes:[NSIndexSet indexSetWithIndex:rowIndex] byExtendingSelection:NO];
    id editingItem = [[[self detailView] itemAtRow:rowIndex] representedObject]; // Using bindings - items are NSTreeNodes
    
    BOOL editingItemIsListItem = [editingItem isKindOfClass:[RNListItem class]];
    [self setEditViewController:(editingItemIsListItem ? (RNEditViewController *)[self editListItemViewController] : (RNEditViewController *)[self editItemViewController])];
    [[self editViewController] setEditingItem:editingItem];
    NSView *editView = [[self editViewController] view];
    [[self detailView] noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndex:rowIndex]];
    NSRect cellFrame = [[self detailView] rectOfRow:rowIndex];
    cellFrame.size.height -= [[self detailView] intercellSpacing].height; // rectOfRow includes intercell spacing - we don't want that
    if (editingItemIsListItem) {
        cellFrame.size.height += 2 * RNEditListItemShadowRadius;
        cellFrame.origin.y -= RNEditListItemShadowRadius;
    }
    [editView setFrame:cellFrame];
    [[self detailView] addSubview:editView];
    [[NSApp mainWindow] makeFirstResponder:[[self editViewController] titleField]];
}


#pragma mark -
#pragma mark Behavior methods

-(void)reloadDetailView {
    if (![self rootObject])
        return;
    // Remove observation of old headers' arrangedObjects
    for (RNDetailHeader *oldHeader in [self currentDetailHeaders]) {
        [[oldHeader primaryController] removeObserver:self forKeyPath:@"arrangedObjects"];
    }
    [self setCurrentSortDescriptors:nil];
    [self setCurrentDetailHeaders:[RNDetailHeader arrayOfHeadersWithRootObject:[self rootObject] filteredWithObject:[self storeFilter] inManagedObjectContext:[self managedObjectContext]]];
    // Add self as observer of headers' arrangedObjects - used to ensure outline view stays expanded when items are added
    for (RNDetailHeader *newHeader in [self currentDetailHeaders]) {
        [[newHeader primaryController] addObserver:self forKeyPath:@"arrangedObjects" options:NSKeyValueObservingOptionNew context:nil];
    }
    NSArray *sortDescriptors = nil;
    if ([[self rootObject] isKindOfClass:[RNHistory class]]) 
        sortDescriptors = [self loggedDateSortDescriptors];
    else if ([[self rootObject] isKindOfClass:[RNStore class]]) 
        sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"aisleNumber" ascending:YES]];
	else if ([[self rootObject] isKindOfClass:[RNList class]] && [self storeFilter]) {
		NSSortDescriptor *displayIndexDescriptor = [[NSSortDescriptor alloc] initWithKey:RNDisplayIndexKey ascending:YES];
		NSSortDescriptor *aisleDescriptor = [[NSSortDescriptor alloc] initWithKey:@"aisleNumber" ascending:YES];
		NSSortDescriptor *titleSortDescriptor = [[NSSortDescriptor alloc] initWithKey:RNTitleKey ascending:YES];
		sortDescriptors = [NSArray arrayWithObjects:displayIndexDescriptor, aisleDescriptor, titleSortDescriptor, nil];
	}
    else
        sortDescriptors = [self titleAndIndexSortDescriptors];
    [self setCurrentSortDescriptors:sortDescriptors];
    [[self detailView] expandItem:nil expandChildren:YES];
    NSUInteger itemCount = [[self detailView] numberOfRows];
    for (NSUInteger selectionIndex = 0; selectionIndex < itemCount; selectionIndex++) {
        NSTreeNode *node = [[self detailView] itemAtRow:selectionIndex];
        if (![self outlineView:[self detailView] shouldSelectItem:node])
            continue;
        [[self detailView] selectRowIndexes:[NSIndexSet indexSetWithIndex:selectionIndex] byExtendingSelection:NO];
        return;
    }
}

-(IBAction)editSelectedItem:(id)sender {
    NSInteger selectedRow = [[self detailView] selectedRow];
    if (selectedRow == -1) {
        NSBeep();
        return;
    }

    NSManagedObject *object = [[[self detailView] itemAtRow:selectedRow] representedObject];  // Using bindings - outlineview items are NSTreeNodes
    if ([object isKindOfClass:[RNHistoryItem class]]) {
        NSBeep();
        return;
    }
    if ([object isKindOfClass:[RNListItem class]] || [object isKindOfClass:[RNItem class]]) {
        [self showEditItemViewAtRow:selectedRow];
        return;
    }
	if ([object isKindOfClass:[RNCategory class]] || [object isKindOfClass:[RNAisle class]]) {
		[[self detailView] editColumn:0 row:selectedRow withEvent:nil select:YES];
		return;
	}
    
    [self setRootObject:object];
}

-(void)removeObject:(NSManagedObject *)object displayWarning:(BOOL)displayWarning {
    if (displayWarning) {
        // TODO: SHOW WARNING DIALOG
    }
    [[self managedObjectContext] deleteObject:object];
    [[self managedObjectContext] processPendingChanges]; // TODO: NEED TO PROCESS CHANGES?
}


#pragma mark -
#pragma mark DetailView Delegate Methods

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item {
    item = [item representedObject]; // using bindings - item is an NSTreeNode
    if (![item isKindOfClass:[NSManagedObject class]])
        return NO;
    if ([item isKindOfClass:[RNCategory class]] && [RNCategory showsChildren])
        return NO;
    if ([item isKindOfClass:[RNAisle class]] && [RNAisle showsChildren])
        return NO;
    
    return YES;
}

-(void)outlineViewSelectionDidChange:(NSNotification *)notification {
    [[self editViewController] hideEditItemView];
}

-(BOOL)outlineView:(NSOutlineView *)outlineView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    return YES;
}

- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    if (outlineView != [self detailView])
        return;
    [cell setRepresentedObject:[item representedObject]];
    [cell setTag:(item == [[self detailView] validatedDropNode] ? RNDragDropTagHighlighted : RNDragDropTagUnhighlighted)];
    NSInteger itemRow = [[self detailView] rowForItem:item];
    if (itemRow == -1 || ![[item representedObject] isKindOfClass:[RNListItem class]] || ![[(RNListItem *)[item representedObject] complete] boolValue] || ![cell isKindOfClass:[RNListItemCell class]])
        return;
    RNListItemCell *listItemCell = (RNListItemCell *)cell;
    id upperItem = [[[self detailView] itemAtRow:(itemRow - 1)] representedObject];
    if ([upperItem isKindOfClass:[RNListItem class]])
        [listItemCell setShouldDrawUpperShadow:![[upperItem complete] boolValue]]; 
    else
        [listItemCell setShouldDrawUpperShadow:NO];
    id lowerItem = [[[self detailView] itemAtRow:(itemRow + 1)] representedObject];
    if ([lowerItem isKindOfClass:[RNListItem class]])
        [listItemCell setShouldDrawLowerShadow:![[lowerItem complete] boolValue]];
    else
        [listItemCell setShouldDrawLowerShadow:NO];
}

- (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    if (outlineView != [self detailView])
        return [self printCellForItem:item];
    id representedObject = [item representedObject]; // Using bindings to NSTreeController, item is an NSTreeNode
    NSString *key = RNEmptyCellKey;
    if ([representedObject isKindOfClass:[RNDetailHeader class]])
        key = RNDetailHeaderCellKey;
    else if ([representedObject isKindOfClass:[SearchSubHeader class]])
        key = RNSearchSubheaderCellKey;
    else if ([representedObject isKindOfClass:[RNHistoryItem class]])
        key = RNHistoryItemCellKey;
    else if ([representedObject isKindOfClass:[RNCategory class]])
        key = ([RNCategory showsChildren] ? RNListContainerCellKey : RNContainerCellKey);
    else if ([representedObject isKindOfClass:[RNAisle class]])
        key = ([RNAisle showsChildren] ? RNListContainerCellKey : RNContainerCellKey);
    else if (representedObject != [[self editViewController] editingItem]) 
        key = [[representedObject entity] name];

    NSCell *dataCell = [[self detailCells] valueForKey:key];
    return dataCell;
}

-(CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item {
    if (outlineView != [self detailView]) 
        return [self printHeightForItem:item];
    id representedObject = [item representedObject]; // Using bindings to NSTreeController, item is an NSTreeNode
    if ([representedObject isKindOfClass:[RNDetailHeader class]]) 
        return [representedObject headerHeight];
    if ([representedObject isKindOfClass:[SearchSubHeader class]])
        return RNHeaderHeight;
    if (representedObject == [[self editViewController] editingItem])
        return [[self editViewController] preferredHeightForWidth:NSWidth([[self detailView] frame])];
    if ([representedObject isKindOfClass:[RNCategory class]])
        return [RNCategory showsChildren] ? RNListContainerCellHeight : RNGenericCellHeight;
    if ([representedObject isKindOfClass:[RNAisle class]])
        return [RNAisle showsChildren] ? RNListContainerCellHeight : RNGenericCellHeight;
    
    return RNGenericCellHeight;
}

- (NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc item:(id)item mouseLocation:(NSPoint)mouseLocation {
    NSAttributedString *titlestring = [cell attributedStringValue];
    if (!titlestring)
        return nil;
    NSRect titleRect = [cell titleRectForBounds:[outlineView rectOfRow:[outlineView rowForItem:item]]];
    if ([titlestring size].width <= NSWidth(titleRect))
        return nil;
    return [[item representedObject] title];
}


#pragma mark -
#pragma mark Utility methods


-(NSString *)buildSheetTitleFromArray:(NSArray *)arrayOfTitles {
    if ([arrayOfTitles count] == 1)
        return [arrayOfTitles objectAtIndex:0];
    
    NSMutableString *compositeTitleString = [NSMutableString string];
    BOOL twoItems = [arrayOfTitles count] == 2;
    for (NSString *titleString in arrayOfTitles) {
        BOOL lastTitle = [arrayOfTitles lastObject] == titleString;
        if (lastTitle) 
            [compositeTitleString appendString:(twoItems ? @" and " : @"and ")];
        [compositeTitleString appendString:titleString];
        if (!lastTitle && !twoItems)
            [compositeTitleString appendString:@", "];
    }
    return compositeTitleString;
}

+(NSSet *)keyPathsForValuesAffectingIsRootObjectOfTypeList {
    return [NSSet setWithObject:@"windowController.rootObject"];
}

-(BOOL)isRootObjectOfTypeList {
    return [[self rootObject] isKindOfClass:[RNList class]];
}

-(void)updateStoreFilterSelectionWithStore:(RNStore *)store {
    NSArrayController *storesController = [(MainWindowController *)[self windowController] storeFilterArrayController];
    NSArray *arrangedObjects = [storesController arrangedObjects];
    BOOL storeContainedInController = [arrangedObjects containsObject:store];
    if (store != nil && !storeContainedInController)
        return;
    [[(MainWindowController *)[self windowController] storeFilterPopUp] selectItemWithTitle:store == nil ? @"All Stores" : [store title]];;    
}

-(BOOL)allSelectedItemsAreListItems_ {
    NSArray *selectedItems = [[self detailTreeController] selectedObjects];
    if ([selectedItems count] == 0) 
        return NO;
    NSArray *selectedNonListItems = [selectedItems filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name != %@", RNEntityNameListItem]];
    if ([selectedNonListItems count] > 0) 
        return NO;
    return YES;
}

@end
